package com.example.oj_springboot.component;



import com.example.oj_springboot.model.Answer;
import com.example.oj_springboot.model.Question;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

//表示编译+运行的过程
public class Task {
    //表示临时文件所在的目录
    private String WORK_DIR;
    //约定代码的类名
    private String CLASS = "Solution";
    //约定要编译执行的代码的文件名
    private String CODE;
    //约定存放运行时标准输出文件名
    private String STDOUT;
    //约定存放运行时标准错误文件名
    private String STDERR;
    //约定存放编译错误信息的文件名
    private String COMPILE_ERROR;

    public Task() {
        //生成唯一的id，根据这个id来拼装出目录的名字
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
        //再生成后续的文件名
        CODE = WORK_DIR + CLASS + ".java";
        STDOUT = WORK_DIR + "stdout.txt";
        STDERR = WORK_DIR + "stderr.txt";
        COMPILE_ERROR = WORK_DIR + "compile_error.txt";
    }

    public Answer compileAndRun(Question question) throws IOException, InterruptedException {
        Answer answer = new Answer();

        //0.准备用来存放临时文件的目录
        File file = new File(WORK_DIR);
        //判断WORD_DIR是否存在
        if (!file.exists()) {
            //不存在就创建对应的目录
            file.mkdirs();
        }
        //进行安全性判定
        if(!checkCodeSafe(question.getCode())){
            System.out.println("用户提交了不安全代码");
            answer.setError(3);
            answer.setReason("您提交的代码不安全");
        }

        //1.把question中的code写入到Solution.java文件中
        FileUtil.writeFile(CODE, question.getCode());

        //2.通过javac进行编译，-d表示生成的.class文件放置的位置
        String compileCmd = String.format("javac -encoding utf-8 %s -d %s", CODE, WORK_DIR);
        System.out.println("编译命令: " + compileCmd);
        //创建子进程进行编译,javac的标准输出没输出啥内容，所以只关心javac的标准错误.标准错误中就包含了编译出错的信息
        CommandUtil.run(compileCmd, null, COMPILE_ERROR);
        //如果编译出的文件COMPILE_ERROR不为空，则表示编译出错
        //byte[] bytes = Files.readAllBytes(Paths.get(COMPILE_ERROR));

        //String compileError = new String(bytes, Charset.forName("GBK"));
        String compileError = FileUtil.readFile(COMPILE_ERROR);
        if (!compileError.equals("")) {
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }

        //3.通过java进行执行，-classpath通过这个选项来执行，表示生成的.class文件放置的位置
        String runCmd = String.format("java -classpath %s %s", WORK_DIR, CLASS);
        System.out.println("runCmd: " + runCmd);
        //创建子进程进行编译，生成标准输出的文件和标准错误的文件，如果程序抛出异常，异常的调用栈信息就是通过stderr来输出的
        CommandUtil.run(runCmd, STDOUT, STDERR);
        //如果执行出的文件STDERR不为空, 则表示运行出错
        String runError = FileUtil.readFile(STDERR);
        if (!runError.equals("")) {
            answer.setError(2);
            answer.setReason(runError);
            return answer;
        }

        //4.如果编译+执行成功，构造对应的answer并返回
        answer.setError(0);
        String runStdout = FileUtil.readFile(STDOUT);
        answer.setStdout(runStdout);
        return answer;
    }

    //进行安全性判定
    private boolean checkCodeSafe(String code) {
        List<String> blackList = new ArrayList<>();
        //防止提交的代码运行恶意程序
        blackList.add("Runtime");
        blackList.add("exec");
        //禁止提交的代码读写文件
        blackList.add("java.io");
        //禁止提交的代码访问网络
        blackList.add("java.net");
        for (String target:blackList) {
            int pos = code.indexOf(target);
            if(pos>=0){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Task task = new Task();
        Question question = new Question();
        question.setCode("public class Solution {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"stdout\");\n" +
                "        String s = null;\n" +
                "        System.out.println(s.length());\n" +
                "    }\n" +
                "}\n");
        Answer answer = task.compileAndRun(question);
        System.out.println(answer);
    }

}
